﻿using System;
using System.Threading.Tasks;
using System.Timers;
using Devonline.Core;
using Devonline.Entity;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;

namespace Devonline.MessageClient
{
    public class ChannelClient<T>
    {
        private static bool _isRunning = false;
        private const string CLIENT_ID = "?clientId=";
        private readonly Timer _timer;
        private readonly HubConnection _connection;
        private readonly ClientSetting _appSetting;
        private readonly ILogger<ChannelClient<T>> _logger;

        /// <summary>
        /// 使用服务器和客户端编号初始化
        /// </summary>
        /// <param name="logger"></param>
        /// <param name="clientSetting"></param>
        public ChannelClient(ILogger<ChannelClient<T>> logger, ClientSetting clientSetting)
        {
            _logger = logger;
            _appSetting = clientSetting;
            _timer = new Timer(_appSetting.MonitorInterval * AppSettings.UNIT_THOUSAND)
            {
                Enabled = true,
                AutoReset = true
            };

            try
            {
                _logger.LogInformation("Message Client will connect {server} use {clientId}", Server, ClientId);

                _connection = new HubConnectionBuilder()
                    .WithUrl(Url, HttpTransportType.WebSockets | HttpTransportType.LongPolling)
                    .AddNewtonsoftJsonProtocol(config => config.PayloadSerializerSettings = AppSettings.JsonSerializerSettings)
                    .WithAutomaticReconnect()
                    .Build();
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Message Client initial error");
            }
        }

        /// <summary>
        /// IM 服务器地址
        /// </summary>
        public string Server => _appSetting.Server;
        /// <summary>
        /// IM 客户端编号
        /// </summary>
        public string ClientId => _appSetting.ClientId;
        /// <summary>
        /// IM 本地链接地址
        /// </summary>
        public string Url => Server + CLIENT_ID + ClientId;
        /// <summary>
        /// 链接状态
        /// </summary>
        public HubConnectionState ConnectionState => _connection.State;

        /// <summary>
        /// 启动连接
        /// </summary>
        /// <returns></returns>
        public async Task StartAsync()
        {
            try
            {
                _isRunning = true;
                _logger.LogWarning("Message Client now will start to connect the server {server} use {clientId}", Server, ClientId);

                //关闭重连
                _connection.Closed += ConnectAsync;

                //断线重连
                _connection.Reconnected += HeartbeatAsync;

                _timer.Elapsed += MonitorAsync;
                _timer.Start();

                //启动连接
                await ConnectAsync();
            }
            catch (Exception ex)
            {
                _isRunning = false;
                _timer.Stop();
                _timer.Elapsed -= MonitorAsync;
                _timer.Dispose();
                _connection.Closed -= ConnectAsync;
                _connection.Reconnected -= HeartbeatAsync;
                _logger.LogError(ex, "Message Client {clientId} start to connect server {server} error", ClientId, Server);
            }
        }

        /// <summary>
        /// 关闭连接
        /// </summary>
        /// <returns></returns>
        public async Task StopAsync()
        {
            try
            {
                _isRunning = false;
                _timer.Stop();
                _timer.Elapsed -= MonitorAsync;
                _timer.Dispose();
                _connection.Closed -= ConnectAsync;
                _connection.Reconnected -= HeartbeatAsync;
                await _connection.StopAsync();
                await _connection.DisposeAsync();
                _logger.LogWarning("Message Client {clientId} now closed the server {server}", ClientId, Server);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Message Client {clientId} close the server {server} error", ClientId, Server);
            }
        }

        #region server call client methods
        /// <summary>
        /// 注册客户端方法, 接受 Message<T> 作为数据的方法
        /// 客户端在收到服务器消息时，将先发送回执，在执行处理方法
        /// </summary>
        /// <param name="methodName"></param>
        /// <param name="action"></param>
        public void On(string methodName, Action<Message<T>> action = null)
        {
            _connection.Remove(methodName);
            _connection.On<Message<T>>(methodName, message =>
            {
                try
                {
                    _logger.LogInformation("Message Client {clientId} receive the the {messageType} message {message} from {sender}", ClientId, message.Type, message.Content, message.Sender);
                    message.ReceiveTime = DateTime.Now;
                    if (message.Type < MessageType.Ack && message.Sender != ClientId)
                    {
                        new Task(async () => await AckAsync(message)).Start();
                    }

                    action?.Invoke(message);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Message Client {clientId} receive the message {message} from {sender} throw exception", ClientId, message.ToJsonString(), message.Sender);
                }
            });
        }

        /// <summary>
        /// 注册客户端方法, 接收字符串主键作为数据的方法
        /// 此时不发送回执给服务器端
        /// </summary>
        /// <param name="methodName"></param>
        /// <param name="action"></param>
        public void On(string methodName, Action<string> action = null)
        {
            _connection.Remove(methodName);
            _connection.On<string>(methodName, messageId =>
            {
                try
                {
                    _logger.LogDebug("Message Client {clientId} {method} the message of {messageId}", ClientId, methodName, messageId);
                    action?.Invoke(messageId);
                }
                catch (Exception ex)
                {
                    _logger.LogError(ex, "Message Client {clientId} {method} the message of {messageId} throw exception", ClientId, methodName, messageId);
                }
            });
        }

        /// <summary>
        /// 注册接受消息的方法
        /// </summary>
        /// <param name="action"></param>
        public void OnReceive(Action<Message<T>> action = null) => On("Receive", action);

        /// <summary>
        /// 注册客户端接收通知消息的方法
        /// </summary>
        /// <param name="action"></param>
        public void OnNotice(Action<Message<T>> action = null) => On(MessageType.Notice.ToString(), action);

        /// <summary>
        /// 注册客户端接收预警消息的方法
        /// </summary>
        /// <param name="action"></param>
        public void OnAlarm(Action<Message<T>> action = null) => On(MessageType.Alarm.ToString(), action);

        /// <summary>
        /// 注册客户端接收回复消息的方法
        /// </summary>
        /// <param name="action"></param>
        public void OnReply(Action<Message<T>> action = null) => On(MessageType.Reply.ToString(), action);

        /// <summary>
        /// 注册客户端接收回执消息的方法
        /// </summary>
        /// <param name="action"></param>
        public void OnAck(Action<string> action = null) => On(MessageType.Ack.ToString(), action);

        /// <summary>
        /// 注册客户端接收回执消息的方法
        /// </summary>
        /// <param name="action"></param>
        public void OnRead(Action<string> action = null) => On("Read", action);
        #endregion

        #region client call server methods
        /// <summary>
        /// 发送消息
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task SendAsync(Message<T> message)
        {
            message.Id = KeyGenerator.GetKey().ToString();
            message.Sender ??= ClientId;
            message.SendTime ??= DateTime.UtcNow;
            await InvokeAsync("Send", message);
        }

        /// <summary>
        /// 发送通知
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task NoticeAsync(Message<T> message)
        {
            message.Id = KeyGenerator.GetKey().ToString();
            message.Sender ??= ClientId;
            message.SendTime ??= DateTime.Now;
            message.Type = MessageType.Notice;
            await InvokeAsync(MessageType.Notice.ToString(), message);
        }

        /// <summary>
        /// 发送消息回执
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task<T> AckAsync(Message<T> message) => await InvokeAsync(MessageType.Ack.ToString(), message, true);

        /// <summary>
        /// 发送消息回执
        /// </summary>
        /// <param name="message"></param>
        /// <returns></returns>
        public async Task<T> ReadAsync(Message<T> message) => await InvokeAsync("Read", message, true);

        /// <summary>
        /// 默认的调用服务器的执行方法
        /// </summary>
        /// <param name="methodName"></param>
        /// <param name="message"></param>
        /// <param name="onlyId"></param>
        /// <returns></returns>
        private async Task<T> InvokeAsync(string methodName, Message<T> message, bool onlyId = false)
        {
            _logger.LogInformation("Message Client {clientId} will {method} the message {message} to {sender}", ClientId, methodName, message.Content, message.Sender);
            return await _connection.InvokeAsync<T>(methodName, onlyId ? message.Id : message);
        }
        #endregion

        /// <summary>
        /// 发送心跳
        /// </summary>
        /// <returns></returns>
        public async Task HeartbeatAsync(string id)
        {
            try
            {
                var message = new Message
                {
                    Id = id ?? _connection.ConnectionId,
                    Type = MessageType.Heartbeat,
                    Sender = ClientId,
                    SendTime = DateTime.UtcNow,
                    CreateTime = DateTime.UtcNow,
                    To = ClientType.Server
                };

                _logger.LogDebug("Message Client {clientId} will send the Heartbeat message to {server}", ClientId, Server);
                await _connection.InvokeAsync<T>(MessageType.Heartbeat.ToString(), message);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Message Client {clientId} send the Heartbeat message to {server} throw exception", ClientId, Server);
            }
        }

        /// <summary>
        /// 客户端连接异常关闭时, 自动发起重新连接
        /// </summary>
        /// <param name="exception"></param>
        /// <returns></returns>
        private async Task ConnectAsync(Exception exception = null)
        {
            try
            {
                if (_isRunning)
                {
                    if (exception != null)
                    {
                        _logger.LogError(exception, "Message Client close the connect of the server {server} use {clientId} with error, now will reconnect later}", Server, ClientId);
                    }

                    if (_connection.State != HubConnectionState.Connected)
                    {
                        await _connection.StartAsync();
                    }
                }
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Message Client {clientId} reconnect the {server} throw exception, it will retry {interval} seconds later", ClientId, Server, _appSetting.MonitorInterval);
            }
        }

        /// <summary>
        /// 客户端连接监控
        /// </summary>
        /// <returns></returns>
        private async void MonitorAsync(object sender, ElapsedEventArgs args)
        {
            if (!_isRunning)
            {
                _timer.Stop();
                return;
            }

            if (_connection.State != HubConnectionState.Connected)
            {
                _logger.LogWarning("Message Client {clientId} disconnected from {server}, now will reconnect", ClientId, Server);
                await ConnectAsync();
            }
            else
            {
                await HeartbeatAsync(_connection.ConnectionId);
            }
        }
    }
}
